﻿using System;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.Reflection;
using System.Text;

namespace PublicDomain.Framework.UnitTesting.DataDrivenTest
{
    public static class DataRowSerializer
    {
        public static T ReadObject<T>(this DataRow dataRow) where T : new()
        {
            var result = new T();
            var resultType = typeof(T);

            var propertInfos = resultType.GetProperties(BindingFlags.Instance | BindingFlags.Public);

            foreach (var propertyInfo in propertInfos)
            {
                var columnName = GetColumnName(propertyInfo, dataRow);

                if (String.IsNullOrWhiteSpace(columnName))
                {
                    continue;
                }

                result.SetPropertyValue(propertyInfo, dataRow, columnName);
            }

            return result;
        }

        private static string GetColumnName(PropertyInfo propertyInfo, DataRow dataRow)
        {
            var dataTableColumnNameAttributes = propertyInfo.GetCustomAttributes(typeof(DataTableColumnNameAttribute), false);

            if (dataTableColumnNameAttributes.Length > 0)
            {
                return (dataTableColumnNameAttributes[0] as DataTableColumnNameAttribute).ColumnName;
            }
            else
            {
                return GetDefaultColumnName(propertyInfo, dataRow);
            }
        }

        private static string GetDefaultColumnName(PropertyInfo propertyInfo, DataRow dataRow)
        {
            if (dataRow.Table.Columns.Contains(propertyInfo.Name))
            {
                return propertyInfo.Name;
            }

            return null;
        }

        private static void SetPropertyValue(this object obj, PropertyInfo propertyInfo, DataRow dataRow, string columnName)
        {
            var columnValue = dataRow[columnName];
            string columnText = null;
            if (columnValue != DBNull.Value)
            {
                columnText = dataRow[columnName].ToString();
                if ("$null$".Equals(columnText, StringComparison.OrdinalIgnoreCase))
                {
                    columnText = null;
                }
                else if ("$ZeroLengthString$".Equals(columnText, StringComparison.OrdinalIgnoreCase))
                {
                    columnText = String.Empty;
                }
                else if ("$\"\"$".Equals(columnText, StringComparison.OrdinalIgnoreCase))
                {
                    columnText = "\"\"";
                }
                else if ("\\t".Equals(columnText, StringComparison.OrdinalIgnoreCase))
                {
                    columnText = "\t";
                }
            }

            if (propertyInfo.PropertyType.IsAssignableFrom(columnValue.GetType()))
            {
                if (propertyInfo.PropertyType == typeof(string))
                {
                    propertyInfo.SetValue(obj, columnText, null);
                }
                else
                {
                    propertyInfo.SetValue(obj, columnValue, null);
                }

                return;
            }

            if (propertyInfo.PropertyType == typeof(double) && dataRow[columnName] == DBNull.Value)
            {
                propertyInfo.SetValue(obj, Double.NaN, null);
                return;
            }

            if (propertyInfo.PropertyType == typeof(float) && dataRow[columnName] == DBNull.Value)
            {
                propertyInfo.SetValue(obj, Single.NaN, null);
                return;
            }

            if (propertyInfo.PropertyType == typeof(Type))
            {
                var propertyTypeValue = Type.GetType(columnText);
                propertyInfo.SetValue(obj, propertyTypeValue, null);
                return;
            }

            if (propertyInfo.PropertyType.IsEnum)
            {
                if (dataRow[columnName] != DBNull.Value)
                {
                    var propertyEnumValue = Enum.Parse(propertyInfo.PropertyType, columnText);
                    propertyInfo.SetValue(obj, propertyEnumValue, null);
                    return;
                }
                else
                {
                    propertyInfo.SetValue(obj, default(Enum), null);
                    return;
                }
            }

            if (propertyInfo.PropertyType == typeof(Guid))
            {
                if (dataRow[columnName] != DBNull.Value)
                {
                    var propertyGuidValue = Guid.Parse(columnText);
                    propertyInfo.SetValue(obj, propertyGuidValue, null);
                    return;
                }
                else
                {
                    propertyInfo.SetValue(obj, default(Guid), null);
                    return;
                }
            }

            if (propertyInfo.PropertyType == typeof(char) && dataRow[columnName] == DBNull.Value)
            {
                propertyInfo.SetValue(obj, default(char), null);
                return;
            }

            if (IsNullable(propertyInfo.PropertyType))
            {
                if (dataRow[columnName] == DBNull.Value || String.IsNullOrEmpty(columnText))
                {
                    propertyInfo.SetValue(obj, null, null);
                }
                else
                {
                    var propertyDescriptors = TypeDescriptor.GetProperties(obj);
                    PropertyDescriptor propertyDescriptor = propertyDescriptors[propertyInfo.Name];
                    propertyDescriptor.SetValue(obj, propertyDescriptor.Converter.ConvertFromInvariantString(columnText));
                }

                return;
            }

            if (propertyInfo.PropertyType == typeof(byte[]) && columnValue.GetType() == typeof(string))
            {
                var propertyTypeValue = new byte[columnText.Length * 2];
                Encoding.Unicode.GetBytes(columnText, 0, columnText.Length, propertyTypeValue, 0);
                
                propertyInfo.SetValue(obj, propertyTypeValue, null);
                return;
            }

            if (propertyInfo.PropertyType == typeof(DateTimeOffset))
            {
                if (dataRow[columnName] != DBNull.Value)
                {
                    var propertyDateTimeOffsetValue = DateTimeOffset.Parse(columnText);
                    propertyInfo.SetValue(obj, propertyDateTimeOffsetValue, null);
                    return;
                }
                else
                {
                    propertyInfo.SetValue(obj, default(Enum), null);
                    return;
                }
            }

            if (propertyInfo.PropertyType == typeof(TimeSpan))
            {
                if (dataRow[columnName] != DBNull.Value)
                {
                    var propertyDateTimeOffsetValue = TimeSpan.Parse(columnText);
                    propertyInfo.SetValue(obj, propertyDateTimeOffsetValue, null);
                    return;
                }
                else
                {
                    propertyInfo.SetValue(obj, default(Enum), null);
                    return;
                }
            }

            var propertyValue = Convert.ChangeType(columnValue, propertyInfo.PropertyType, CultureInfo.InvariantCulture);
            propertyInfo.SetValue(obj, propertyValue, null);
        }

        private static bool IsNullable(Type type)
        {
            var result = type.IsValueType && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);

            return result;
        }
    }
}
